/*
 * Package il.ac.biu.cs.grossmm.api_tests.data
 * File SubscribeNotifyTest.java
 * 
 * This is a part of presence service framework API. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package il.ac.biu.cs.grossmm.junit_api_tests.data;

import static il.ac.biu.cs.grossmm.api.data.NodeTypeByInterface.nodeType;
import static il.ac.biu.cs.grossmm.api.keys.KeyFactory.key;
import static il.ac.biu.cs.grossmm.api.keys.PatternFactory.optional;
import static il.ac.biu.cs.grossmm.api.keys.PatternFactory.pattern;
import static il.ac.biu.cs.grossmm.api.presence.BaseVocabulary.WATCHER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import il.ac.biu.cs.grossmm.api.OperationFailedException;
import il.ac.biu.cs.grossmm.api.data.Node;
import il.ac.biu.cs.grossmm.api.data.Root;
import il.ac.biu.cs.grossmm.api.data.UpdateType;
import il.ac.biu.cs.grossmm.api.flow.ActiveDataManager;
import il.ac.biu.cs.grossmm.api.flow.NoSuchSubscription;
import il.ac.biu.cs.grossmm.api.flow.NodeEventInspector;
import il.ac.biu.cs.grossmm.api.flow.NodeInspector;
import il.ac.biu.cs.grossmm.api.flow.NodeManipulator;
import il.ac.biu.cs.grossmm.api.flow.NotificationPoint;
import il.ac.biu.cs.grossmm.api.flow.Notifier;
import il.ac.biu.cs.grossmm.api.flow.Status;
import il.ac.biu.cs.grossmm.api.flow.Subscriber;
import il.ac.biu.cs.grossmm.api.flow.SubscriptionPoint;
import il.ac.biu.cs.grossmm.api.flow.Track;
import il.ac.biu.cs.grossmm.api.keys.Key;
import il.ac.biu.cs.grossmm.api.keys.KeyPattern;
import il.ac.biu.cs.grossmm.api.presence.Pidf;
import il.ac.biu.cs.grossmm.api.presence.PidfTuple;
import il.ac.biu.cs.grossmm.api.server.Component;
import il.ac.biu.cs.grossmm.api.server.ComponentManager;
import il.ac.biu.cs.grossmm.api.server.ServiceManager;
import il.ac.biu.cs.grossmm.impl.activeData.ActiveDataManagerImpl;
import il.ac.biu.cs.grossmm.impl.server.RegistryServer;
import il.ac.biu.cs.grossmm.api.server.Service;

import java.util.Set;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class SubscribeNotifyTest implements Subscriber<Pidf>, Notifier<Pidf>,
		Component, Service {

	RegistryServer server = new RegistryServer();

	ActiveDataManager adm;

	ServiceManager sm;

	SubscriptionPoint<Pidf> sp;

	NotificationPoint<Pidf> np;

	Lock lock = new ReentrantLock();

	Condition condition = lock.newCondition();

	private Key inputKey;

	private Root<Pidf> inputNode;


	private NodeEventInspector inspector;

	private NodeInspector ins;

	private Key outputKey;

	private Root<Pidf> outputNode;

	Inline subTest = null;

	Inline notTest = null;

	Inline actTest = null;

	abstract class Inline extends Thread {
		@Override
		public void run() {
			try {
				test();
			} catch (Exception e) {
				e.printStackTrace();
				fail();
			}
		}

		abstract public void test() throws Exception;
	}

	@Before
	public void startUp() throws Exception {
		server.register(ActiveDataManager.class, new ActiveDataManagerImpl());
		// server.register(PersistentPointManager.class, new SqlPointManager());

		server.register(ActiveDataManagerTest.class, this);
		server.addToStartup(ActiveDataManagerTest.class);

		server.start();
		assert adm != null;

		KeyPattern pattern = pattern(String.class, optional(WATCHER,
				String.class));

		np = adm
				.createNotificationPoint(this, pattern, nodeType(Pidf.class), 1);
		sp = adm.createSubscriptionPoint(this, pattern, nodeType(Pidf.class));

		inputKey = null;
		inputNode = null;
		outputKey = null;
		outputNode = null;

		inspector = null;

		subTest = null;
		notTest = null;

	}

	@After
	public void tearDown() {
		server.stop();
	}

	public void start(ServiceManager serviceManager) throws Exception {
		sm = serviceManager;
		System.out.println("TEST STARTED");
	}

	public void stop(Exception exception) {
		System.out.println("TEST STOPPED " + exception);
	}

	public void terminated(Object serviceHint, Exception e) {
		System.out.println("TERMINATED " + serviceHint + " : " + e);
		sm.terminated(e);
	}

	public void aborted(Object hint, Exception e) {
		System.out.println("ABORTED " + hint + " : " + e);
	}

	public void setComponentManager(ComponentManager container)
			throws Exception {
		adm = (ActiveDataManager) container
				.getComponent(ActiveDataManager.class);
	}

	public void activated(Key key, Root<Pidf> node) {
		assertNull(inputKey);

		inputKey = key;
		inputNode = node;

		runActTest();
	}

	public void deactivated(Key key, Status status) throws NoSuchSubscription {
		assertTrue(inputKey == null || status == Status.ACTIVE);

		inputNode = null;
	}

	public void notify(Root<Pidf> node, NodeEventInspector inspector) {
		assertNotNull(node);
		assertSame(node, inputNode);

		this.inspector = inspector;

		System.out.println("NOTIFICATION: " + inspector.toString(node));

		runNotTest();
	}

	public void fetch(Key key, Track track, Root<Pidf> node,
			NodeManipulator manipulator) {
		// TODO Implement fetch
		// 
		throw new RuntimeException("Not implemented");
	}

	public boolean subscribe(Key key, Track track) throws Exception {
		outputKey = key;
		outputNode = np.activated(key);

		runSubTest();

		return true;
	}

	public void unsubscribe(Key key) {
		// TODO Implement unsubscribe
		// 
		throw new RuntimeException("Not implemented");
	}

	void reser() {
		subTest = null;
		notTest = null;
		actTest = null;
	}

	void runSubTest() {
		if (subTest != null)
			subTest.run();
	}

	void runNotTest() {
		if (notTest != null)
			notTest.run();
	}

	void runActTest() {
		if (actTest != null)
			actTest.run();
	}

	interface Tester {
		void intercept();
	}

	@Test
	public void test1() {
		Key key = key("Presentity");

		Status status = sp.subscribe(key, null);

		assertEquals(key, outputKey);
		assertEquals(Status.ACTIVATING, status);
	}

	@Test
	public void test2() {
		Key key = key("Presentity");

		subTest = new Inline() {
			public void test() {
				np.writeLock(outputNode);
				np.writeUnlock(outputNode);

			}
		};

		Status status = sp.subscribe(key, null);
		assertEquals(key, outputKey);
		assertEquals(Status.ACTIVE, status);
	}

	@Test
	public void test3() throws OperationFailedException {
		Key key = key("Presentity");

		subTest = new Inline() {
			public void test() {
				try {
					NodeManipulator man = np.writeLock(outputNode);
					man.setAndCheck(outputNode, Pidf.ENTITY,
							"alice@example.com");
					np.writeUnlock(outputNode);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		};

		actTest = new Inline() {

			@Override
			public void test() throws OperationFailedException {
				ins = sp.readLock(inputNode);

				String entity = ins.getValue(inputNode, Pidf.ENTITY);
				assertEquals("alice@example.com", entity);

				sp.readUnlock(inputNode);
			}

		};

		Status status = sp.subscribe(key, null);
		assertEquals(Status.ACTIVE, status);

		notTest = new Inline() {

			@Override
			public void test() throws OperationFailedException {
				assertSame(UpdateType.CHANGED, inspector
						.getUpdateType(inputNode));

				String entity = inspector.getValue(inputNode, Pidf.ENTITY);
				assertEquals("alice2@example.com", entity);

				String oldEntity = inspector
						.getOldValue(inputNode, Pidf.ENTITY);
				assertEquals("alice@example.com", oldEntity);
				System.out.println("TESTED!!!!!!!!!!!!!!!!!!!!!!!");

			}
		};

		NodeManipulator man = np.writeLock(outputNode);
		man.setValue(outputNode, Pidf.ENTITY, "alice2@example.com");
		np.writeUnlock(outputNode);

		notTest = new Inline() {

			@Override
			public void test() throws OperationFailedException {
				assertSame(UpdateType.CHANGED, inspector
						.getUpdateType(inputNode));

				String entity = inspector.getValue(inputNode, Pidf.ENTITY);
				assertEquals("alice3@example.com", entity);

				String oldEntity = inspector
						.getOldValue(inputNode, Pidf.ENTITY);
				assertEquals("alice2@example.com", oldEntity);
				System.out.println("TESTED!!!!!!!!!!!!!!!!!!!!!!!");

			}
		};

		man = np.writeLock(outputNode);
		man.setValue(outputNode, Pidf.ENTITY, "alice3@example.com");
		np.writeUnlock(outputNode);

	}

	@Test
	public void test4() throws OperationFailedException {
		Key key = key("Presentity");

		subTest = new Inline() {
			public void test() {
				try {
					NodeManipulator man = np.writeLock(outputNode);
					man.setAndCheck(outputNode, Pidf.ENTITY,
							"alice@example.com");
					np.writeUnlock(outputNode);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		};

		actTest = new Inline() {

			@Override
			public void test() throws OperationFailedException {
				ins = sp.readLock(inputNode);

				String entity = ins.getValue(inputNode, Pidf.ENTITY);
				assertEquals("alice@example.com", entity);

				sp.readUnlock(inputNode);
			}

		};

		Status status = sp.subscribe(key, null);
		assertEquals(Status.ACTIVE, status);

		// //////////////

		NodeManipulator man = np.writeLock(outputNode);
		final Node<PidfTuple> t1 = man.createSubnode(outputNode, Pidf.TUPLES);
		final Node<PidfTuple> t2 = man.createSubnode(outputNode, Pidf.TUPLES);
		man.setValue(t2, PidfTuple.ID, "123");

		notTest = new Inline() {

			@Override
			public void test() throws OperationFailedException {
				assertSame(UpdateType.CHANGED, inspector
						.getUpdateType(inputNode));

				String entity = inspector.getValue(inputNode, Pidf.ENTITY);
				assertEquals("alice@example.com", entity);

				String oldEntity = inspector
						.getOldValue(inputNode, Pidf.ENTITY);
				assertEquals("alice@example.com", oldEntity);

				Set<Node<PidfTuple>> subnodes = inspector.getSubnodes(
						inputNode, Pidf.TUPLES);
				Set<Node<PidfTuple>> unchanged = inspector.getSubnodes(
						inputNode, Pidf.TUPLES, UpdateType.UNCHANGED);
				Set<Node<PidfTuple>> changed = inspector.getSubnodes(inputNode,
						Pidf.TUPLES, UpdateType.CHANGED);
				Set<Node<PidfTuple>> added = inspector.getSubnodes(inputNode,
						Pidf.TUPLES, UpdateType.ADDED);
				Set<Node<PidfTuple>> removed = inspector.getSubnodes(inputNode,
						Pidf.TUPLES, UpdateType.REMOVED);

				assertEquals(2, subnodes.size());
				assertEquals(0, unchanged.size());
				assertEquals(0, changed.size());
				assertEquals(2, added.size());
				assertEquals(0, removed.size());

				assertTrue(subnodes.equals(added));

				assertTrue(added.contains(t1));
				assertTrue(subnodes.contains(t2));
			}
		};

		np.writeUnlock(outputNode);

		// ////////////////

		notTest = new Inline() {

			@Override
			public void test() throws OperationFailedException {
				assertSame(UpdateType.CHANGED, inspector
						.getUpdateType(inputNode));

				String entity = inspector.getValue(inputNode, Pidf.ENTITY);
				assertEquals("alice3@example.com", entity);

				String oldEntity = inspector
						.getOldValue(inputNode, Pidf.ENTITY);
				assertEquals("alice@example.com", oldEntity);

			}
		};

		man = np.writeLock(outputNode);
		man.setValue(outputNode, Pidf.ENTITY, "alice3@example.com");
		np.writeUnlock(outputNode);

	}

	@Test
	public void test5() throws OperationFailedException {
		Key key = key("Presentity");

		subTest = new Inline() {
			public void test() {
				try {
					NodeManipulator man = np.writeLock(outputNode);
					man.setAndCheck(outputNode, Pidf.ENTITY,
							"alice@example.com");
					np.writeUnlock(outputNode);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		};

		actTest = new Inline() {

			@Override
			public void test() throws OperationFailedException {
				ins = sp.readLock(inputNode);

				String entity = ins.getValue(inputNode, Pidf.ENTITY);
				assertEquals("alice@example.com", entity);

				sp.readUnlock(inputNode);
			}

		};

		Status status = sp.subscribe(key, null);
		assertEquals(Status.ACTIVE, status);

		// //////////////

		NodeManipulator man = np.writeLock(outputNode);
		
		final Node<PidfTuple> t1 = man.createSubnode(outputNode, Pidf.TUPLES);
		final Node<PidfTuple> t2 = man.createSubnode(outputNode, Pidf.TUPLES);
		
		// set id's for them
		man.setValue(t1, PidfTuple.ID, "123");
		man.setValue(t2, PidfTuple.ID, "345");

		notTest = new Inline() {

			@Override
			public void test() throws OperationFailedException {
				assertSame(t1, inspector.getSubnode(inputNode, Pidf.TUPLES,
						PidfTuple.ID, "123"));
				assertSame(t2, inspector.getSubnode(inputNode, Pidf.TUPLES,
						PidfTuple.ID, "345"));
				assertNull(inspector.getSubnode(inputNode, Pidf.TUPLES,
						PidfTuple.ID, "678"));
				
				assertEquals(UpdateType.CHANGED, inspector.getUpdateType(inputNode));
				assertEquals(UpdateType.ADDED, 
						inspector.getUpdateType(inspector.getSubnode(
								inputNode, Pidf.TUPLES,
						PidfTuple.ID, "345")));
			}
		};

		np.writeUnlock(outputNode);

		// ////////////////
		man = np.writeLock(outputNode);
		
		// create a tuple t3
		final Node<PidfTuple> t3 = man.createSubnode(outputNode, Pidf.TUPLES);
		
		// delete previously created tuple t1
		man.removeSubnode(t1);

		notTest = new Inline() {

			@Override
			public void test() throws OperationFailedException {
				assertSame(UpdateType.CHANGED, inspector
						.getUpdateType(inputNode));

				String entity = inspector.getValue(inputNode, Pidf.ENTITY);
				assertEquals("alice@example.com", entity);

				String oldEntity = inspector
						.getOldValue(inputNode, Pidf.ENTITY);
				assertEquals("alice@example.com", oldEntity);

				Set<Node<PidfTuple>> subnodes = inspector.getSubnodes(
						inputNode, Pidf.TUPLES);
				Set<Node<PidfTuple>> unchanged = inspector.getSubnodes(
						inputNode, Pidf.TUPLES, UpdateType.UNCHANGED);
				Set<Node<PidfTuple>> changed = inspector.getSubnodes(inputNode,
						Pidf.TUPLES, UpdateType.CHANGED);
				Set<Node<PidfTuple>> added = inspector.getSubnodes(inputNode,
						Pidf.TUPLES, UpdateType.ADDED);
				Set<Node<PidfTuple>> removed = inspector.getSubnodes(inputNode,
						Pidf.TUPLES, UpdateType.REMOVED);

				assertEquals(2, subnodes.size());
				assertEquals(1, unchanged.size());
				assertEquals(0, changed.size());
				assertEquals(1, added.size());
				assertEquals(1, removed.size());

				assertFalse(subnodes.contains(t1));
				assertTrue(subnodes.contains(t2));
				assertTrue(subnodes.contains(t3));

				assertTrue(removed.contains(t1));
				assertTrue(unchanged.contains(t2));
				assertTrue(added.contains(t3));
			}
		};
		
		

		np.writeUnlock(outputNode);

		// ////////////////
		man = np.writeLock(outputNode);
		man.setValue(outputNode, Pidf.ENTITY, "alice0@example.com");

		notTest = new Inline() {

			@Override
			public void test() throws OperationFailedException {
				assertSame(UpdateType.CHANGED, inspector
						.getUpdateType(inputNode));

				String entity = inspector.getValue(inputNode, Pidf.ENTITY);
				assertEquals("alice0@example.com", entity);

				String oldEntity = inspector
						.getOldValue(inputNode, Pidf.ENTITY);
				assertEquals("alice@example.com", oldEntity);

				Set<Node<PidfTuple>> subnodes = inspector.getSubnodes(
						inputNode, Pidf.TUPLES);
				Set<Node<PidfTuple>> unchanged = inspector.getSubnodes(
						inputNode, Pidf.TUPLES, UpdateType.UNCHANGED);
				Set<Node<PidfTuple>> changed = inspector.getSubnodes(inputNode,
						Pidf.TUPLES, UpdateType.CHANGED);
				Set<Node<PidfTuple>> added = inspector.getSubnodes(inputNode,
						Pidf.TUPLES, UpdateType.ADDED);
				Set<Node<PidfTuple>> removed = inspector.getSubnodes(inputNode,
						Pidf.TUPLES, UpdateType.REMOVED);

				assertEquals(2, subnodes.size());
				assertEquals(2, unchanged.size());
				assertEquals(0, changed.size());
				assertEquals(0, added.size());
				assertEquals(0, removed.size());

				assertTrue(subnodes.contains(t2));
				assertTrue(subnodes.contains(t3));
			}
		};

		np.writeUnlock(outputNode);

		// ////////////////
		man = np.writeLock(outputNode);
		man.setValue(outputNode, Pidf.ENTITY, "alice00@example.com");

		notTest = new Inline() {

			@Override
			public void test() throws OperationFailedException {
				assertSame(UpdateType.CHANGED, inspector
						.getUpdateType(inputNode));

				String entity = inspector.getValue(inputNode, Pidf.ENTITY);
				assertEquals("alice00@example.com", entity);

				String oldEntity = inspector
						.getOldValue(inputNode, Pidf.ENTITY);
				assertEquals("alice0@example.com", oldEntity);

				Set<Node<PidfTuple>> subnodes = inspector.getSubnodes(
						inputNode, Pidf.TUPLES);
				Set<Node<PidfTuple>> unchanged = inspector.getSubnodes(
						inputNode, Pidf.TUPLES, UpdateType.UNCHANGED);
				Set<Node<PidfTuple>> changed = inspector.getSubnodes(inputNode,
						Pidf.TUPLES, UpdateType.CHANGED);
				Set<Node<PidfTuple>> added = inspector.getSubnodes(inputNode,
						Pidf.TUPLES, UpdateType.ADDED);
				Set<Node<PidfTuple>> removed = inspector.getSubnodes(inputNode,
						Pidf.TUPLES, UpdateType.REMOVED);

				assertEquals(2, subnodes.size());
				assertEquals(2, unchanged.size());
				assertEquals(0, changed.size());
				assertEquals(0, added.size());
				assertEquals(0, removed.size());

				assertTrue(subnodes.contains(t2));
				assertTrue(subnodes.contains(t3));
				
				assertEquals(UpdateType.UNCHANGED, inspector.getUpdateType(t2));
				assertEquals(UpdateType.UNCHANGED, inspector.getUpdateType(t3));
				assertEquals(UpdateType.CHANGED, inspector.getUpdateType(inputNode));
			}
		};

		np.writeUnlock(outputNode);
		
	}
}
